package com.zp.ojcodesandbox;

import cn.hutool.core.io.FileUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.Volume;
import com.zp.ojcodesandbox.model.ExecuteCodeRequest;
import com.zp.ojcodesandbox.model.ExecuteCodeSandResponse;
import com.zp.ojcodesandbox.model.ExecuteMessage;
import com.zp.ojcodesandbox.model.JudgeInfo;
import com.zp.ojcodesandbox.util.ProcessRunAndMessage;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
public abstract class CodeSandeBoxTemplate implements CodeSandBox {
    private static final String Global_Code_Dir_Name = "tmpCode";
    private static final String Define_Java_class_name = "Main.java";
    static final String Define_cpp_class_Name = "Main.cpp";
    static final String Define_c_class_name = "Main.c";
    static final HashMap<String, String> fineNameEnum = new HashMap<>();

    static {
        fineNameEnum.put("java", "Main.java");
        fineNameEnum.put("cpp", "Main.cpp");
        fineNameEnum.put("c", "Main.c");
    }

    private static final Long Java_Run_Time = 1000L;

    /**
     * 将代码保存为文件
     * @param code
     * @return
     */
    public File createFile(String code, String language){
        // 搜索具有指定名称的公共属性
        String userDir = System.getProperty("user.dir");
        // 使用File.separator文件分隔符提高适用性
        String globalCodePathName = userDir + File.separator + Global_Code_Dir_Name;
        // 不存在文件就创建
        if(!FileUtil.exist(globalCodePathName)){
            FileUtil.mkdir(globalCodePathName);
        }
        // 将用户代码保存成文件
        String userCodeDir = globalCodePathName + File.separator + UUID.randomUUID();
        String userCodePath = userCodeDir + File.separator + fineNameEnum.get(language);
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
        return userCodeFile;
    }

    /**
     * 编译文件
     * @param userCodeFile
     * @return
     */
    public ExecuteMessage compliedFile(File userCodeFile){
        // 编译代码
        String complied = String.format("javac -encoding utf-8 " + userCodeFile.getAbsolutePath());
        try {
            // 运行控制台语句
            Process compiledProcess = Runtime.getRuntime().exec(complied);
            ExecuteMessage executeMessage = ProcessRunAndMessage.execute(compiledProcess, "complied");
            return executeMessage;
        }catch (Exception e){
            throw new RuntimeException("编译失败", e);
        }
    }

    /**
     * 运行文件
     *
     * @param executeCodeRequest
     * @param userCodeFile
     * @return
     */
    public List<ExecuteMessage> runFile(ExecuteCodeRequest executeCodeRequest, File userCodeFile){
        String userCodeDir = userCodeFile.getParentFile().getAbsolutePath();
        List<ExecuteMessage> errorManagerList = new ArrayList<>();
        for (String inputArgs: executeCodeRequest.getInputs()){
            String runCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeDir, inputArgs );
            try {
                Process runProcess = Runtime.getRuntime().exec(runCmd);
                // 超时控制
                new Thread(() -> {
                    try {
                        Thread.sleep(Java_Run_Time);
                        Integer exitValue = runProcess.exitValue();
                        if(exitValue == null){
                            System.out.println("over time");
                            runProcess.destroy();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
                ExecuteMessage runMessage = ProcessRunAndMessage.execute(runProcess, "运行");
                errorManagerList.add(runMessage);
            } catch (IOException e) {
                throw new RuntimeException("运行失败", e);
            }
        }
        return errorManagerList;
    }

    public ExecuteCodeSandResponse jointMessage(List<ExecuteMessage> errorManagerList){
        ExecuteCodeSandResponse executeCodeSandResponse = new ExecuteCodeSandResponse();
        // 最长时间
        Long maxTime = 0L;
        Long maxMemory = 0L;
        List<String> outputsList = new ArrayList<>();
        executeCodeSandResponse.setStatus(1);
        for (ExecuteMessage executeMessage: errorManagerList){
            if(executeMessage.getTime() != null){
                maxTime = Math.max(maxTime, executeMessage.getTime());
            }
            if(executeMessage.getMemory() != null){
                maxMemory = Math.max(maxMemory, executeMessage.getMemory());
            }
            if(executeMessage.getErrorMessage() != null && executeMessage.getErrorMessage().length() != 0){
                executeCodeSandResponse.setMessage(executeMessage.getErrorMessage());
                executeCodeSandResponse.setStatus(3);
                break;
            }
            outputsList.add(executeMessage.getInputMessage());
        }
        executeCodeSandResponse.setOutputs(outputsList);
        JudgeInfo judgeInfo = new JudgeInfo();
        judgeInfo.setTime(maxTime);
        judgeInfo.setMemory(maxMemory);
        executeCodeSandResponse.setJudgeInfo(judgeInfo);
        return executeCodeSandResponse;
    }

    public Boolean deleteFile(File userCodeFile){
        Boolean delete = true;
        if(userCodeFile.getParentFile() != null){
             delete = FileUtil.del(userCodeFile.getParentFile());
             log.info("delete File" + (delete ? "成功": "失败"));
        }
        return delete;
    }

    public Boolean deleteDocker(DockerClient dockerClient, String containId){
        Boolean delete = true;
        dockerClient.stopContainerCmd(containId).exec();
        dockerClient.removeContainerCmd(containId).withForce(true).exec();
        return delete;
    }
    @Override
    public ExecuteCodeSandResponse executeCode(ExecuteCodeRequest executeCodeRequest) {

        List<String> inputs = executeCodeRequest.getInputs();
        String code = executeCodeRequest.getCode();
        String language = executeCodeRequest.getLanguage();

        // 创建文件
        File userCodeFile = createFile(code, language);

        // 编译
        ExecuteMessage executeMessage = compliedFile(userCodeFile);
        if(executeMessage.getErrorMessage() != null){
            deleteFile(userCodeFile);
            return jointMessage(Arrays.asList(executeMessage));
        }

        // 执行代码
        List<ExecuteMessage> errorManagerList = runFile(executeCodeRequest, userCodeFile);
        // 拼接返回信息

        ExecuteCodeSandResponse executeCodeSandResponse = jointMessage(errorManagerList);
        executeCodeSandResponse.setLanguage(executeCodeRequest.getLanguage());

        // 文件清理
        boolean delete = deleteFile(userCodeFile);
        log.error("删除%s", delete ? "success":"fail");
        return executeCodeSandResponse;
    }

    /**
     * 创建容器
     * @param userCodeDir
     * @param dockerClient
     * @param image
     * @return
     */
    public CreateContainerResponse dockerCreateContainer(String userCodeDir, DockerClient dockerClient, String image){
        // 选择创建容器的镜像
        CreateContainerCmd createContainerCmd = dockerClient.createContainerCmd(image);
        // 项目初始配置设置
        HostConfig hostConfig = new HostConfig();
        hostConfig.setBinds(new Bind(userCodeDir, new Volume("/app")));
        hostConfig.withMemorySwap(0L);
        // 单位b
        hostConfig.withMemory(100 * 1000 * 1000L);
        hostConfig.withCpuCount(1L);
        // hostConfig.withSecurityOpts(Arrays.asList("seccomp=安全管理字符串")); //linux自带的安全管理
        // 设置可以交互终端 非常重要
        CreateContainerResponse createContainerResponse = createContainerCmd
                .withNetworkDisabled(true)  // 限制网络
                .withHostConfig(hostConfig)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)  // 很重要模式设置成可交互模式
                .withReadonlyRootfs(false) // 不能对根目录写文件
                .exec();
        return  createContainerResponse;
    }

    /**
     * 错误处理方法 返回上述代码无法处理的错误信息
     * @param e
     * @param executeCodeRequest
     * @return
     */
    private ExecuteCodeSandResponse getErrorResponse(Throwable e, ExecuteCodeRequest executeCodeRequest){
        ExecuteCodeSandResponse executeCodeSandResponse = new ExecuteCodeSandResponse();
        executeCodeSandResponse.setOutputs(new ArrayList<>());
        executeCodeSandResponse.setMessage(e.getMessage());
        executeCodeSandResponse.setStatus(2);
        executeCodeSandResponse.setLanguage(executeCodeRequest.getLanguage());
        executeCodeSandResponse.setJudgeInfo(new JudgeInfo());
        return executeCodeSandResponse;
    }
}
